<?php

namespace common\components;

use Yii;
use \Memcached;

/**
 * memcached缓存类
 * 
 * @author qixiaopeng <qixiaopeng@ledoing.cn>
 */
class MemcachedCache extends \yii\base\Object
{

    /**
     *
     * @var boolean 是否在实例化时自动连接服务器
     */
    public $autoConnect = false;

    /**
     *
     * @var string 长连接的KEY值
     */
    public $persistentId = 'lechuang';

    /**
     *
     * @var int 异步模式下的超时时间，单位毫秒，默认1000
     */
    public $timeout = 2000;

    /**
     *
     * @var array 服务器列表，支持多个服务器
     */
    public $servers = [];

    /**
     *
     * @var boolean 是否已经建立连接
     */
    public $connected = false;

    /**
     *
     * @var mixed Memcached类的实例
     */
    public $cache = null;

    /**
     *
     * @var boolean 是否开启缓存
     */
    public $enable = true;

    public function init()
    {
        parent::init();
        $this->connect();
    }

    /**
     * 建立到memache服务器的连接
     */
    public function connect()
    {
        if ($this->connected == true) {
            return true;
        }
        $servers = $this->getServers();
        $cache = $this->getMemCached();
        if (empty($servers) || !is_array($servers) || count($servers) <= 0) {
            throw new Exception('Memcached Servers undefined');
        }
        $i = 0;
        $newServers = [];
        foreach ($servers as $server) {
            if (!isset($server['host']) || !isset($server['port'])) {
                continue;
            }
            if (!isset($server['weight'])) {
                $server['weight'] = 1;
            }
            $newServers[] = $server;
            $i ++;
        }
        if ($i == 0) {
            throw new Exception('Memcached can\'t add any servers');
        }
        if (!$cache->addServers($newServers)) {
            throw new Exception("Memcached addServers failure: " . $cache->getResultMessage());
        }
        $this->connected = true;
        return true;
    }

    /**
     *
     * @return array
     */
    public function getServers()
    {
        return $this->servers;
    }

    /**
     *
     * @param array $servers
     */
    public function setServers(array $servers)
    {
        $this->servers = $servers;
    }

    /**
     *
     * @return Memcached
     */
    public function getMemcached()
    {
        if (!is_null($this->cache)) {
            return $this->cache;
        }
        if (empty($this->persistentId)) {
            $this->cache = new Memcached();
        } else {
            $this->cache = new Memcached($this->persistentId);
        }
        $options = [
            Memcached::OPT_RECV_TIMEOUT => $this->timeout,
            Memcached::OPT_SEND_TIMEOUT => $this->timeout,
            Memcached::OPT_CONNECT_TIMEOUT => $this->timeout,
            Memcached::OPT_POLL_TIMEOUT => $this->timeout,
            Memcached::OPT_DISTRIBUTION => Memcached::DISTRIBUTION_CONSISTENT, // 使用一致性哈希
            Memcached::OPT_HASH => Memcached::HASH_MD5, // 使用MD5 KEY哈希算法
            Memcached::OPT_LIBKETAMA_COMPATIBLE => true, // 开启兼容libketama算法
            Memcached::OPT_BINARY_PROTOCOL => true,
            Memcached::OPT_NO_BLOCK => true,
        ];
        if (!$this->cache->setOptions($options)) {
            throw new Exception("Memcached setOptions failure: " . $this->cache->getResultMessage());
        }
        return $this->cache;
    }

    public function checkKey($key)
    {
        if (!is_string($key)) {
            throw new Exception('Memcached: 缓存key必须是一个字符串');
        }
        if (strlen($key) > 512) {
            throw new Exception('Memcached: 缓存key必须小于512');
        }
    }

    public function checkExpiry($expiry, $incrementExpiry = false)
    {
        if ($expiry == 0) {
            throw new Exception('Memcached: 不能设置缓存超时时间为0');
        }
        if ($incrementExpiry == false) {
            return $expiry;
        }
        // 增加随机过期时间，1-5分钟
        $expiry += rand(60, 300);
        return $expiry;
    }

    /**
     * 设置一个缓存
     * @param string $key
     * @param mixed $value
     * @param integer $expiry
     */
    public function set($key, $value, $expiry = 3600, $incrementExpiry = false)
    {
        if ($this->enable == false) {
            return false;
        }
        $expiry = $this->checkExpiry($expiry, $incrementExpiry);
        $this->checkKey($key);
        if (!$this->cache->set($key, $value, $expiry)) {
            Yii::warning("Memcache set failure, key({$key}): " . $this->cache->getResultMessage());
            return false;
        }
        return true;
    }

    /**
     * 查询一个缓存
     * @param string $key
     * @return mixed 如果查询不到缓存值，则记录一个警告日志，并且返回FALSE
     * @throws Exception
     */
    public function get($key, &$casToken = null)
    {
        if ($this->enable == false) {
            return false;
        }
        $this->checkKey($key);
        if (($cacheData = $this->cache->get($key, null, $casToken)) === false) {
            Yii::warning("Memcache get failure, key({$key}): " . $this->cache->getResultMessage());
            return false;
        }
        return $cacheData;
    }

    /**
     * 批量设置缓存，使用统一的超时时间
     * @param array $items
     * @param type $expiry
     */
    public function setMulti(array $items, $expiry = 3600, $incrementExpiry = false)
    {
        if ($this->enable == false) {
            return false;
        }
        if (empty($items)) {
            return false;
        }
        $expiry = $this->checkExpiry($expiry, $incrementExpiry);
        if (!$this->cache->setMulti($items, $expiry)) {
            Yii::warning('Memcache setMulti failure, key(' . implode(',', array_keys($items)) . '): ' . $this->cache->getResultMessage());
            return false;
        }
        return true;
    }

    /**
     * 批量查询缓存
     * @param array $keys
     */
    public function getMulti(array $keys, &$casToken = null)
    {
        if ($this->enable == false) {
            return false;
        }
        if (empty($keys)) {
            return false;
        }
        if (($cacheData = $this->cache->getMulti($keys, $casToken)) === false) {
            Yii::warning('Memcache getMulti failure, key(' . implode(',', $keys) . '): ' . $this->cache->getResultMessage());
            return false;
        }
        return $cacheData;
    }

    /**
     * 删除一个缓存
     * @param string $key
     * @return boolean
     */
    public function delete($key)
    {
        if ($this->enable == false) {
            return false;
        }
        $this->checkKey($key);
        if (!($this->cache->delete($key))) {
            Yii::warning("Memcache delete failure, key({$key}): " . $this->cache->getResultMessage());
            return false;
        }
        return true;
    }

    /**
     * 批量删除缓存
     * @param array $keys
     */
    public function deleteMulti(array $keys)
    {
        if ($this->enable == false) {
            return false;
        }
        if (!($this->cache->deleteMulti($keys))) {
            Yii::warning("Memcache deleteMulti failure, key(" . implode(',', $keys) . "): " . $this->cache->getResultMessage());
            return false;
        }
        return true;
    }

    /**
     * 使用乐观锁进行安全的缓存更新
     * @param type $casToken
     * @param type $key
     * @param type $value
     * @param type $expiry
     * @param type $incrementExpiry
     */
    public function cas($casToken, $key, $value, $expiry = 3600, $incrementExpiry = false)
    {
        if ($this->enable == false) {
            return false;
        }
        $expiry = $this->checkExpiry($expiry, $incrementExpiry);
        $this->checkKey($key);
        if (!$this->cache->cas($casToken, $key, $value, $expiry)) {
            Yii::warning("Memcache cas failure, key({$key}): " . $this->cache->getResultMessage());
            return false;
        }
        return true;
    }
}